package org.evolAI;

import java.awt.EventQueue;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JMenu;
import javax.swing.JSeparator;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

import java.awt.BorderLayout;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.UUID;

import javax.swing.JButton;

import org.apache.log4j.Logger;
import org.evolAI.organism.domain.Organism;
import org.evolAI.organism.Population;

import javax.swing.JTextArea;
import javax.swing.JTable;
import javax.swing.filechooser.FileFilter;
import javax.swing.table.AbstractTableModel;
import javax.swing.text.MaskFormatter;
import javax.swing.text.NumberFormatter;
import javax.swing.ListSelectionModel;
import javax.swing.JScrollPane;
import javax.swing.JPanel;
import java.awt.Dimension;
import javax.swing.JTextField;
import javax.swing.JSpinner;
import javax.swing.JLabel;
import javax.swing.SpinnerNumberModel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import javax.swing.JFormattedTextField;

public class evolUI {

	private final static Logger logger = Logger.getLogger(evolUI.class);
	
	private OrganismTableModel tableModel;
	private Population orgs = new Population();
	private JFileChooser fcSelectOrg;

	private JFrame frame;
	private JTable table;
	private JTextField txtFilter;
	private JTextField txtFind;
	private JTextArea txtIn;
	private JSpinner spnGenerations;
	private JSpinner spnOffspring;
	private final JPanel pnlStatus = new JPanel();
	private JLabel lblMessage;
	private JLabel lblCount;
	private JFormattedTextField txtMaxDepth;
	private JFormattedTextField txtMaxSteps;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					evolUI window = new evolUI();
					window.frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public evolUI() {
		Organism org1 = Organism.defaultOrganism();
		orgs.add(org1);
		tableModel = new OrganismTableModel();
		fcSelectOrg = new JFileChooser();
		fcSelectOrg.setAcceptAllFileFilterUsed(false);
		fcSelectOrg.addChoosableFileFilter(new OrgFilter());
		
		initialize();
		bind();
	}

	public void bind() {
		if (SwingUtilities.isEventDispatchThread()) {
			bindTable();
		}
		else {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					bindTable();
				}
			});
		}
	}
	
	private void bindTable() {
		tableModel.loadData(orgs);
		lblCount.setText("count:" + tableModel.getRowCount());
		table.repaint();
	}
	
	private void updatePopulation() {
        for (int i = 0; i < tableModel.getRowCount(); i++) {
            UUID orgId = (UUID)tableModel.getValueAt(i, 11);
            boolean selected = (Boolean)tableModel.getValueAt(i, 1);
            orgs.get(orgId).setSelected(selected);
        }
	}
	
//    private void saveSelected(String filename) {
//        Map<UUID, Organism> selected = new HashMap<UUID, Organism>();
//        for (int i = 0; i < tableModel.getRowCount(); i++) {
//            if ((Boolean)tableModel.getValueAt(i, 1)) {
//                Organism org = orgs.get((UUID)tableModel.getValueAt(i, 11));
//                selected.put(org.getId(), org);
//            }
//        }
//
//        saveChatters(filename, selected);
//    }

//    private void saveChatters(String filename, Map<UUID, Organism> orgs) {
//		try {
//			OutputStream file = new FileOutputStream(filename);
//			OutputStream buffer = new BufferedOutputStream(file);
//			ObjectOutput output = new ObjectOutputStream(new GZIPOutputStream(buffer));
//			try {
//				output.writeObject(orgs);
//			}
//			finally {
//				output.close();
//			}
//		}
//		catch(IOException ex) {
//		}
//    }
	
	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frame = new JFrame();
		frame.setBounds(100, 100, 923, 583);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JMenuBar menuBar = new JMenuBar();
		frame.setJMenuBar(menuBar);
		
		JMenu mnFile = new JMenu("File");
		menuBar.add(mnFile);
		
		JMenuItem mntmOpen = new JMenuItem("Open");
		mntmOpen.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_mntmOpen_actionPerformed(e);
			}
		});
		mnFile.add(mntmOpen);
		
		JMenuItem mntmOpenGroup = new JMenuItem("Open Group");
		mntmOpenGroup.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_mntmOpenGroup_actionPerformed(e);
			}
		});
		mnFile.add(mntmOpenGroup);
		
		JMenuItem mntmSave = new JMenuItem("Save");
		mntmSave.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_mntmSave_actionPerformed(e);
			}
		});
		mnFile.add(mntmSave);
		
		JMenuItem mntmSaveSelectedGroup = new JMenuItem("Save Selected Group");
		mntmSaveSelectedGroup.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_mntmSaveSelectedGroup_actionPerformed(e);
			}
		});
		mnFile.add(mntmSaveSelectedGroup);
		
		JSeparator separator = new JSeparator();
		mnFile.add(separator);
		
		JMenuItem mntmExit = new JMenuItem("Exit");
		mntmExit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				do_mntmExit_actionPerformed(arg0);
			}
		});
		mnFile.add(mntmExit);
		
		JMenu mnEdit = new JMenu("Edit");
		menuBar.add(mnEdit);
		
		JMenuItem mntmReset = new JMenuItem("Reset");
		mntmReset.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_mntmReset_actionPerformed(e);
			}
		});
		mnEdit.add(mntmReset);
		frame.getContentPane().setLayout(new BorderLayout(0, 0));
		
		JPanel pnlSubmit = new JPanel();
		frame.getContentPane().add(pnlSubmit, BorderLayout.NORTH);
		pnlSubmit.setLayout(new BorderLayout(0, 0));
		
		JToolBar toolBar = new JToolBar();
		pnlSubmit.add(toolBar, BorderLayout.NORTH);
		
		JButton btnSubmit = new JButton("Submit");
		btnSubmit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_btnSubmit_actionPerformed(e);
			}
		});
		toolBar.add(btnSubmit);
		
		JButton btnSubmitSelected = new JButton("Submit Selected");
		btnSubmitSelected.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_btnSubmitSelected_actionPerformed(e);
			}
		});
		toolBar.add(btnSubmitSelected);
		
		txtIn = new JTextArea();
		txtIn.setMinimumSize(new Dimension(12, 100));
		txtIn.setRows(8);
		
				JScrollPane scrollPane = new JScrollPane(txtIn);
				pnlSubmit.add(scrollPane);
				scrollPane.setMinimumSize(new Dimension(25, 200));
		
		JPanel pnlPopulation = new JPanel();
		frame.getContentPane().add(pnlPopulation, BorderLayout.CENTER);
		pnlPopulation.setLayout(new BorderLayout(0, 0));
		
		JToolBar toolbarPopulation = new JToolBar();
		pnlPopulation.add(toolbarPopulation, BorderLayout.NORTH);
		
		JButton btnMutate = new JButton("Mutate");
		toolbarPopulation.add(btnMutate);
		btnMutate.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_btnMutate_actionPerformed(e);
			}
		});
		
		JButton btnClearMemory = new JButton("Clear Memory");
		toolbarPopulation.add(btnClearMemory);
		
		JSeparator separator_2 = new JSeparator();
		toolbarPopulation.add(separator_2);
		
		txtFilter = new JTextField();
		toolbarPopulation.add(txtFilter);
		txtFilter.setColumns(10);
		
		JButton btnFilter = new JButton("Filter");
		toolbarPopulation.add(btnFilter);
		
		txtFind = new JTextField();
		toolbarPopulation.add(txtFind);
		txtFind.setColumns(10);
		
		JButton btnFind = new JButton("Find");
		toolbarPopulation.add(btnFind);
		
		JSeparator separator_1 = new JSeparator();
		toolbarPopulation.add(separator_1);
		
		JLabel lblOffspring = new JLabel(" Offspring");
		toolbarPopulation.add(lblOffspring);
		
		spnOffspring = new JSpinner();
		toolbarPopulation.add(spnOffspring);
		spnOffspring.setModel(new SpinnerNumberModel(new Integer(5), new Integer(0), null, new Integer(1)));
		
		JLabel lblGenerations = new JLabel(" Generations");
		toolbarPopulation.add(lblGenerations);
		
		spnGenerations = new JSpinner();
		toolbarPopulation.add(spnGenerations);
		spnGenerations.setModel(new SpinnerNumberModel(new Integer(5), new Integer(0), null, new Integer(1)));
		
		JSeparator separator_3 = new JSeparator();
		toolbarPopulation.add(separator_3);
		
		JLabel lblNewLabel = new JLabel(" MaxSteps");
		toolbarPopulation.add(lblNewLabel);
		
		txtMaxSteps = new JFormattedTextField(createFormatter("#######"));
		txtMaxSteps.setText("10000");
		txtMaxSteps.setPreferredSize(new Dimension(50, 20));
		txtMaxSteps.setMaximumSize(new Dimension(50, 20));
		txtMaxSteps.setMinimumSize(new Dimension(50, 20));
		toolbarPopulation.add(txtMaxSteps);
		
		JLabel lblNewLabel_1 = new JLabel(" MaxDepth");
		toolbarPopulation.add(lblNewLabel_1);
		
		txtMaxDepth = new JFormattedTextField(createFormatter("#######"));
		txtMaxDepth.setText("250");
		txtMaxDepth.setPreferredSize(new Dimension(50, 20));
		txtMaxDepth.setMinimumSize(new Dimension(50, 20));
		txtMaxDepth.setMaximumSize(new Dimension(50, 20));
		toolbarPopulation.add(txtMaxDepth);
		btnFind.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_btnFind_actionPerformed(e);
			}
		});
		btnFilter.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_btnFilter_actionPerformed(e);
			}
		});
		btnClearMemory.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				do_btnClearMemory_actionPerformed(e);
			}
		});
		
		table = new JTable();
		table.setMinimumSize(new Dimension(200, 0));
		table.setMaximumSize(new Dimension(200, 0));
		
				JScrollPane scrollPane_1 = new JScrollPane(table);
				pnlPopulation.add(scrollPane_1);
				
				table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
				table.setShowVerticalLines(true);
				table.setShowHorizontalLines(true);
				table.setModel(tableModel);
				table.createDefaultColumnsFromModel();
				table.getColumnModel().getColumn(0).setResizable(false);
				table.getColumnModel().getColumn(0).setPreferredWidth(74);
				table.getColumnModel().getColumn(0).setMaxWidth(74);
				table.getColumnModel().getColumn(1).setResizable(false);
				table.getColumnModel().getColumn(1).setPreferredWidth(68);
				table.getColumnModel().getColumn(1).setMaxWidth(68);
				table.getColumnModel().getColumn(2).setResizable(false);
				table.getColumnModel().getColumn(2).setPreferredWidth(55);
				table.getColumnModel().getColumn(2).setMaxWidth(55);
				table.getColumnModel().getColumn(3).setResizable(false);
				table.getColumnModel().getColumn(3).setPreferredWidth(43);
				table.getColumnModel().getColumn(3).setMaxWidth(43);
				table.getColumnModel().getColumn(4).setResizable(false);
				table.getColumnModel().getColumn(4).setPreferredWidth(75);
				table.getColumnModel().getColumn(4).setMaxWidth(75);
				table.getColumnModel().getColumn(5).setResizable(false);
				table.getColumnModel().getColumn(5).setMaxWidth(67);
				table.getColumnModel().getColumn(5).setPreferredWidth(67);
				table.getColumnModel().getColumn(6).setResizable(false);
				table.getColumnModel().getColumn(6).setMaxWidth(70);
				table.getColumnModel().getColumn(6).setPreferredWidth(70);
				table.getColumnModel().getColumn(7).setResizable(false);
				table.getColumnModel().getColumn(7).setMaxWidth(99);
				table.getColumnModel().getColumn(7).setPreferredWidth(99);
				table.setFillsViewportHeight(true);
				table.setAutoCreateRowSorter(true);
		frame.getContentPane().add(pnlStatus, BorderLayout.SOUTH);
		pnlStatus.setLayout(new BorderLayout(0, 0));
		
		lblMessage = new JLabel("Message");
		pnlStatus.add(lblMessage, BorderLayout.CENTER);
		
		lblCount = new JLabel("Count:");
		pnlStatus.add(lblCount, BorderLayout.EAST);
	}
	
	protected static void do_mntmExit_actionPerformed(ActionEvent arg0) {
		System.exit(0);
	}
	
	protected void do_mntmOpen_actionPerformed(ActionEvent e) {
		int ret = fcSelectOrg.showOpenDialog(this.frame);
		if (ret == JFileChooser.APPROVE_OPTION) {
            Organism newOrg = Organism.load(fcSelectOrg.getSelectedFile().getAbsolutePath());
            orgs.clear();
            orgs.add(newOrg);
            bind();
		}
	}
	
	protected static void do_mntmOpenGroup_actionPerformed(ActionEvent e) {
	}
	
	protected static void do_mntmSave_actionPerformed(ActionEvent e) {
	}
	
	protected static void do_mntmSaveSelectedGroup_actionPerformed(ActionEvent e) {
		
	}
	
	protected void do_mntmReset_actionPerformed(ActionEvent e) {
		orgs.clear();
		Organism org1 = Organism.defaultOrganism();
		orgs.add(org1);
	}
	
	protected void do_btnSubmit_actionPerformed(ActionEvent e) {
		SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
			protected Void doInBackground() throws InterruptedException {
				lblMessage.setText("Sending message to all organisms ...");
				updatePopulation();
				orgs.exec(txtIn.getText(), Integer.valueOf(txtMaxDepth.getText()), Integer.valueOf(txtMaxSteps.getText()));
				return null;
			}
			protected void done() {
				try {
			        txtIn.setText("");
					bind();
					get();
					lblMessage.setText("Done");
				} catch (InterruptedException ex) {
					logger.fatal("Submit error", ex);
				} catch (ExecutionException ex) {
					logger.fatal("Submit error", ex);
				} catch (CancellationException ex) {}
			}
		};
		worker.execute();
	}
	
	protected void do_btnSubmitSelected_actionPerformed(ActionEvent e) {
		SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
			protected Void doInBackground() throws InterruptedException {
				lblMessage.setText("Sending message to selected organisms ...");
				updatePopulation();
				orgs.execSelected(txtIn.getText(), Integer.valueOf(txtMaxDepth.getText()), Integer.valueOf(txtMaxSteps.getText()));
				return null;
			}
			protected void done() {
				try {
			        txtIn.setText("");
					bind();
					get();
					lblMessage.setText("Done");
				} catch (InterruptedException ex) {
					logger.fatal("Submit error", ex);
				} catch (ExecutionException ex) {
					logger.fatal("Submit error", ex);
				} catch (CancellationException ex) {}
			}
		};
		worker.execute();
	}
	
	protected static void do_btnClearMemory_actionPerformed(ActionEvent e) {
	}
	
	protected static void do_btnFilter_actionPerformed(ActionEvent e) {
	}
	
	protected static void do_btnFind_actionPerformed(ActionEvent e) {
	}
	
	protected void do_btnMutate_actionPerformed(ActionEvent e) {
		SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
			protected Void doInBackground() throws InterruptedException {
				lblMessage.setText("Mutating organisms ...");
				updatePopulation();
				orgs.mutate(txtIn.getText(), (Integer)spnGenerations.getValue(), (Integer)spnOffspring.getValue(), Integer.valueOf(txtMaxDepth.getText()), Integer.valueOf(txtMaxSteps.getText()));
				return null;
			}
			protected void done() {
				try {
					txtIn.setText("");
					bind();
					get();
					lblMessage.setText("Done");
				} catch (InterruptedException ex) {
					logger.fatal("Submit error", ex);
				} catch (ExecutionException ex) {
					logger.fatal("Submit error", ex);
				} catch (CancellationException ex) {}
			}
		};
		worker.execute();
	}
	
	protected JTextArea getTxtIn() {
		return txtIn;
	}
	
	protected NumberFormatter createFormatter(String s) {
		NumberFormat format = new DecimalFormat();
		format.setParseIntegerOnly(true);
		format.setGroupingUsed(false);
		NumberFormatter formatter = new NumberFormatter(format);
	    return formatter;
	}
}

class OrganismTableModel extends AbstractTableModel {
    private String[] columnNames = new String[]{"Generation", "Selected", "Codes", "Steps", "Exec Steps", "Live Time", "Memories", "Memory Groups", "Memory", "Input", "Output"};
	private Class[] columnTypes = new Class[] {Integer.class, Boolean.class, Integer.class, Integer.class, Integer.class, Integer.class, Integer.class, Integer.class, String.class, String.class, String.class};
	private Organism[] orgs = new Organism[0];

	public void loadData(Population orgs) {		
		this.orgs = orgs.values().toArray(new Organism[0]);
		this.fireTableDataChanged();
	}

	public int getRowCount() {
		return orgs.length;
	}

	public int getColumnCount() {
		return columnNames.length;
	}

	public Object getValueAt(int arg0, int arg1) {
		switch (arg1) {
			case 0:
				return orgs[arg0].getGeneration();
			case 1:
				return orgs[arg0].isSelected();
			case 2:
				return orgs[arg0].getCodeCount();
			case 3:
				return orgs[arg0].getStepCount();
			case 4:
				return orgs[arg0].getLastStepCount();
			case 5:
				return orgs[arg0].getLiveTime();
			case 6:
				return orgs[arg0].getMemoryCount();
			case 7:
				return orgs[arg0].getMemoryGroupCount();
			case 8:
				return orgs[arg0].getMemories().dump();
			case 9:
				return orgs[arg0].getInputs();
			case 10:
				return orgs[arg0].getResponses();
			case 11:
				return orgs[arg0].getId();
		}
		return "";
	}

	public void setValueAt(Object value, int row, int col) {
		if (col==1)
			orgs[row].setSelected((Boolean) value);
	}
	
	@Override
	public String getColumnName(int columnIndex) {
        String colName = "";

        if (columnIndex <= getColumnCount())
            colName = columnNames[columnIndex];

        return colName;
    }

	@Override
    public Class getColumnClass(int columnIndex){
		return columnTypes[columnIndex];
    }

	@Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
		if (columnIndex == 1)
			return true;
		return false;
    }

}

class OrgFilter extends FileFilter {

	@Override
	public boolean accept(File f) {
		if (f.isDirectory())
			return true;

        String ext = null;
        String s = f.getName();
        int i = s.lastIndexOf('.');

        if (i > 0 &&  i < s.length() - 1) {
            ext = s.substring(i+1).toLowerCase();
        }
		return (ext==null)?false:ext.equals("organism");
	}

	@Override
	public String getDescription() {
		return "Organism Files";
	}
}